package cz.semjobKB.virtuoso.dao;

import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import virtuoso.jena.driver.VirtGraph;
import virtuoso.jena.driver.VirtuosoQueryExecution;
import virtuoso.jena.driver.VirtuosoQueryExecutionFactory;

import com.hp.hpl.jena.graph.Node;
import com.hp.hpl.jena.graph.Triple;
import com.hp.hpl.jena.query.Query;
import com.hp.hpl.jena.query.QueryFactory;
import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.query.ResultSet;
import com.hp.hpl.jena.rdf.model.RDFNode;

import cz.semjobKB.knowledge.api.IExtractedKnowledge;
import cz.semjobKB.knowledge.api.ITerminologyItem;
import cz.semjobKB.knowledge.api.ITripleItem;
import cz.semjobKB.virtuoso.utils.ELanguage;
import cz.semjobKB.virtuoso.utils.VirtuosoUtils;

@Component
public class VirtuosoDao {

	@Autowired
	VirtuosoUtils virtuosoUtils;
	
	private static Logger logger = Logger.getLogger(VirtuosoDao.class);
	
	@Value( "${db.url}" )
	private String dbUrl;
	
	@Value( "${db.user}" )
	private String dbUser;
	
	@Value( "${db.pass}" )
	private String dbPass;
	
	public VirtuosoDao() {		
	}
	
	public ResultSet executeQuery(String queryString, VirtGraph graph) {
					
		Query sparql = QueryFactory.create(queryString);
		
		if (graph == null) {
			VirtGraph set = new VirtGraph (dbUrl, dbUser, dbPass);

			VirtuosoQueryExecution vqe = VirtuosoQueryExecutionFactory.create (sparql, set);

			return vqe.execSelect();

		}
		
		VirtuosoQueryExecution vqe = VirtuosoQueryExecutionFactory.create (sparql, graph);

		return vqe.execSelect();
	}
	
	public Integer storeTerms(List<ITripleItem> triples, ELanguage language) {
		HashMap<String, String> listOfTerms = new HashMap<String, String>();
		Set<String> uniqueTerms = new HashSet<String>();
		
		logger.info("Storing terms");
		for (ITripleItem triple : triples) {
			if (triple.isCompleteTriple()) {
				listOfTerms.put(triple.getSubjectWord(), triple.getSubjectLemma());
				listOfTerms.put(triple.getObjectWord(), triple.getObjectLemma());
				uniqueTerms.add(triple.getSubjectLemma());
				uniqueTerms.add(triple.getObjectLemma());
			}
		}
		
		VirtGraph termGraph = new VirtGraph(virtuosoUtils.getKnownGraphsUris().get("terms"), dbUrl, dbUser, dbPass);
		
		Node baseTermNode = Node.createURI(virtuosoUtils.getOntologyNodes().get("Term"));
		Node ontoHasLemmaNode = Node.createURI(virtuosoUtils.getOntologyNodes().get("hasLemma"));
		Node ontoHasStringRepresentationNode = Node.createURI(virtuosoUtils.getOntologyNodes().get("hasStringRepresentation"));
		Node ontoSetLanguageNode = Node.createURI(virtuosoUtils.getOntologyNodes().get("language"));
		Node rdfTypeNode = Node.createURI(virtuosoUtils.getOntologyNodes().get("a"));
		Node termLanguageNode = Node.createLiteral(language.name());
		
		for (Map.Entry<String, String> termEntry : listOfTerms.entrySet()) {
			String termLemmaUri = virtuosoUtils.escape(termEntry.getValue().replaceAll(" ", "_"));
			String termLemma = termEntry.getValue();
			String termText = termEntry.getKey();
			
			Node termUriNode = Node.createURI(virtuosoUtils.getKnownGraphsUris().get("terms") + termLemmaUri);
			Node termStringRepresentationNode = Node.createLiteral(termText);
			Node termLemmaNode = Node.createLiteral(termLemma);			
			
			//Term not exists in the DB
			if(!termGraph.contains(termUriNode, rdfTypeNode, baseTermNode)) {				
				
				//new Term is type of semjobKB:Term
				termGraph.add(new Triple(termUriNode, rdfTypeNode, baseTermNode));
				
				//set lemma string to the term
				termGraph.add(new Triple(termUriNode, ontoHasLemmaNode, termLemmaNode));
				
				//sets original string representation for the term
				termGraph.add(new Triple(termUriNode, ontoHasStringRepresentationNode, termStringRepresentationNode));
				
				//sets the language of the new term
				termGraph.add(new Triple(termUriNode, ontoSetLanguageNode, termLanguageNode));
				
				continue;
			}
			
			//Term exists, but string representation is new
			if (!termGraph.contains(termUriNode, ontoHasStringRepresentationNode, termStringRepresentationNode)) {
				
				//sets original string representation for the term
				termGraph.add(new Triple(termUriNode, ontoHasStringRepresentationNode, termStringRepresentationNode));				
			}
			//Term exists with specific string representation, continue to the next term
		}
		
		termGraph.close();
		
		logger.info("Terms: " + listOfTerms.size() + "\t unique terms: " + uniqueTerms.size());
		
		return listOfTerms.size();
	}
	
	public Integer storePredicates(List<ITripleItem> triples, ELanguage language) {
		HashMap<String, String> listOfPredicates = new HashMap<String, String>();
		Set<String> uniquePredicates = new HashSet<String>();
		
		logger.info("Storing Predicates");
		for (ITripleItem triple : triples) {
			if (triple.isCompleteTriple()) {
				listOfPredicates.put(triple.getPredicateWord(), triple.getPredicateLemma());				
				uniquePredicates.add(triple.getPredicateLemma());
			}
		}
		
		VirtGraph predGraph = new VirtGraph(virtuosoUtils.getKnownGraphsUris().get("predicates"), dbUrl, dbUser, dbPass);
		
		Node basePredNode = Node.createURI(virtuosoUtils.getOntologyNodes().get("Predicate"));
		Node ontoHasLemmaNode = Node.createURI(virtuosoUtils.getOntologyNodes().get("hasLemma"));
		Node ontoHasStringRepresentationNode = Node.createURI(virtuosoUtils.getOntologyNodes().get("hasStringRepresentation"));
		Node ontoSetLanguageNode = Node.createURI(virtuosoUtils.getOntologyNodes().get("language"));
		Node rdfTypeNode = Node.createURI(virtuosoUtils.getOntologyNodes().get("a"));
		Node predLanguageNode = Node.createLiteral(language.name());
		
		for (Map.Entry<String, String> termEntry : listOfPredicates.entrySet()) {
			String predLemmaUri = virtuosoUtils.escape(termEntry.getValue().replaceAll(" ", "_"));
			String predLemma = termEntry.getValue();
			String predText = termEntry.getKey();
			
			Node predUriNode = Node.createURI(virtuosoUtils.getKnownGraphsUris().get("predicates") + predLemmaUri);
			Node predStringRepresentationNode = Node.createLiteral(predText);
			Node predLemmaNode = Node.createLiteral(predLemma);			
			
			//Predicate not exists in the DB
			if(!predGraph.contains(predUriNode, rdfTypeNode, basePredNode)) {				
				
				//new Predicate is type of semjobKB:Predicate
				predGraph.add(new Triple(predUriNode, rdfTypeNode, basePredNode));
				
				//set lemma string to the Predicate
				predGraph.add(new Triple(predUriNode, ontoHasLemmaNode, predLemmaNode));
				
				//sets original string representation for the Predicate
				predGraph.add(new Triple(predUriNode, ontoHasStringRepresentationNode, predStringRepresentationNode));
				
				//sets the language of the new Predicate
				predGraph.add(new Triple(predUriNode, ontoSetLanguageNode, predLanguageNode));
				
				continue;
			}
			
			//Term exists, but string representation is new
			if (!predGraph.contains(predUriNode, ontoHasStringRepresentationNode, predStringRepresentationNode)) {
				
				//sets original string representation for the term
				predGraph.add(new Triple(predUriNode, ontoHasStringRepresentationNode, predStringRepresentationNode));				
			}
			//Term exists with specific string representation, continue to the next term
		}
		
		predGraph.close();
		
		return listOfPredicates.size();
	}
	
	public void storeTerminology(VirtGraph documentGraph, List<ITerminologyItem> terminologyList, ELanguage language) {
		
		VirtGraph terminologyGraph = new VirtGraph(virtuosoUtils.getKnownGraphsUris().get("terminology"), dbUrl, dbUser, dbPass);
		
		Node baseTerminologyNode = Node.createURI(virtuosoUtils.getOntologyNodes().get("TerminologyItem"));
		Node ontoHasLemmaNode = Node.createURI(virtuosoUtils.getOntologyNodes().get("hasLemma"));
		Node ontoHasStringRepresentationNode = Node.createURI(virtuosoUtils.getOntologyNodes().get("hasStringRepresentation"));
		Node ontoSetLanguageNode = Node.createURI(virtuosoUtils.getOntologyNodes().get("language"));		
		Node ontoHasFrequency = Node.createURI(virtuosoUtils.getOntologyNodes().get("hasFrequency"));
		Node ontoTotalCountNode = Node.createURI(virtuosoUtils.getOntologyNodes().get("hasTotalCount"));
		Node ontoOverallFrequencyNode = Node.createURI(virtuosoUtils.getOntologyNodes().get("hasOverallFrequency"));		
		Node rdfTypeNode = Node.createURI(virtuosoUtils.getOntologyNodes().get("a"));
		
		Node terminologyLanguageNode = Node.createLiteral(language.name());
		
		String query =  "SELECT * FROM <" + documentGraph.getGraphName() + "> WHERE { ?s "
				+ "<" + ontoHasFrequency.getURI() + "> ?frequency }";
		
		ResultSet result = executeQuery(query, documentGraph);
		
		while(result.hasNext()) {
			QuerySolution resultQuery = result.nextSolution();
			Node termFrequency = resultQuery.get("frequency").asNode();
			Node termLemmaDocuUri = Node.createURI(virtuosoUtils.escape(resultQuery.get("s").asNode().getURI()));
			Integer frequency = Integer.valueOf((String)termFrequency.getLiteralValue());
			System.out.println(termLemmaDocuUri.getURI());
			String queryTerm =  "SELECT ?frequency FROM <" + terminologyGraph.getGraphName() + "> WHERE { <"+ termLemmaDocuUri.getURI() + "> "
					+ "<" + ontoOverallFrequencyNode.getURI() + "> ?frequency }";
			
			ResultSet resultTermDic = executeQuery(queryTerm, terminologyGraph);
		
			if (resultTermDic.hasNext()) {				
				Node oldTotalFrequencyNode = resultTermDic.nextSolution().get("frequency").asNode();
				Integer oldFrequency = Integer.valueOf((String)oldTotalFrequencyNode.getLiteralValue());
				
				Integer newOverallFrequency = oldFrequency - frequency;
				if (newOverallFrequency < 0) {
					logger.error("Frequency cant be below 0! " + termLemmaDocuUri + " " + newOverallFrequency + " " + frequency);						
				} else {
					terminologyGraph.remove(termLemmaDocuUri, ontoOverallFrequencyNode, oldTotalFrequencyNode);
					Node newOverallFrequencyNode = Node.createLiteral(newOverallFrequency.toString());
					terminologyGraph.add(new Triple(termLemmaDocuUri, ontoOverallFrequencyNode, newOverallFrequencyNode));
				}
			}
			
			String updateTerm =  "SELECT ?count FROM <" + terminologyGraph.getGraphName() + "> WHERE { <"+ termLemmaDocuUri.getURI() + "> "
					+ "<" + ontoTotalCountNode.getURI() + "> ?count }";
			
			ResultSet resulTermUpdate = executeQuery(updateTerm, terminologyGraph);
		
			if (resulTermUpdate.hasNext()) {				
				Node oldTotalCountNode = resulTermUpdate.nextSolution().get("count").asNode();
				Integer oldCount = Integer.valueOf((String)oldTotalCountNode.getLiteralValue());
				
				Integer newTotalCount = oldCount - 1;
				if (newTotalCount < 0) {
					logger.error("Frequency cant be below 0! " + termLemmaDocuUri + " " + newTotalCount);						
				} else {
					terminologyGraph.remove(termLemmaDocuUri, ontoTotalCountNode, oldTotalCountNode);
					Node newOverallFrequencyNode = Node.createLiteral(newTotalCount.toString());
					terminologyGraph.add(new Triple(termLemmaDocuUri, ontoTotalCountNode, newOverallFrequencyNode));
				}
			}
			
			
		}
		
		for(ITerminologyItem terminItem : terminologyList) {
			String terminologyLemmaUri = virtuosoUtils.escape(terminItem.getLemma().replaceAll(" ", "_"));
			terminologyLemmaUri = terminologyLemmaUri.trim();
			String terminologyLemma = terminItem.getLemma();
			HashMap<String, Integer> terminologyTextRepresentationSet = terminItem.getOriginalText();
			
			Node terminologyUriNode = Node.createURI(virtuosoUtils.getKnownGraphsUris().get("terminology") + terminologyLemmaUri);
			Node terminologyLemmaNode = Node.createLiteral(terminologyLemma);
			
									
			//Terminology graph contains terminology lemma already, need to add new string representation and update frequency
			if(terminologyGraph.contains(terminologyUriNode, rdfTypeNode, baseTerminologyNode)) {
				
				query =  "SELECT ?frequency FROM <" + terminologyGraph.getGraphName() + "> WHERE { <" + 
						terminologyUriNode.getURI() + "> <" + ontoOverallFrequencyNode.getURI() + "> ?frequency }";
				
				result = executeQuery(query, terminologyGraph);
				
				Node oldOverallFrequencyNode = Node.createLiteral("0");
				Integer overallFrequency = 0;					
				if(result.hasNext()) {
					QuerySolution resultSol = result.nextSolution();
					RDFNode resultNode = resultSol.get("frequency");
					oldOverallFrequencyNode = resultNode.asNode();
					overallFrequency = Integer.valueOf((String)oldOverallFrequencyNode.getLiteralValue());
				}
				//For every string representation of the terminology item add new string representation to the existing one
				for(Map.Entry<String, Integer> terminTextRepresentation : terminologyTextRepresentationSet.entrySet()) {
					Node terminologyTextLiteralNode = Node.createLiteral(terminTextRepresentation.getKey());
					
					if(!terminologyGraph.contains(terminologyUriNode, ontoHasStringRepresentationNode, terminologyTextLiteralNode)) {
						terminologyGraph.add(new Triple(terminologyUriNode, ontoHasStringRepresentationNode, terminologyTextLiteralNode));
					}
				}
				
				Integer newOverallFrequency = overallFrequency + terminItem.getFrequency();				
				terminologyGraph.remove(terminologyUriNode, ontoOverallFrequencyNode, oldOverallFrequencyNode);
				Node newOverallFrequencyNode = Node.createLiteral(newOverallFrequency.toString());
				terminologyGraph.add(new Triple(terminologyUriNode, ontoOverallFrequencyNode, newOverallFrequencyNode));
				
				query =  "SELECT ?count FROM <" + terminologyGraph.getGraphName() + "> WHERE { <" + 
						terminologyUriNode.getURI() + "> <" + ontoTotalCountNode.getURI() + "> ?count }";
				
				result = executeQuery(query, terminologyGraph);
				
				Node oldTotalCountNode = Node.createLiteral("1");
				Integer oldTotalCount = 0;					
				if(result.hasNext()) {
					oldTotalCountNode = result.nextSolution().get("count").asNode();
					oldTotalCount = Integer.valueOf((String)oldTotalCountNode.getLiteralValue());
				}
				
				terminologyGraph.remove(terminologyUriNode, ontoTotalCountNode, oldTotalCountNode);				
				terminologyGraph.add(new Triple(terminologyUriNode, 
						ontoTotalCountNode, 
						Node.createLiteral(String.valueOf(oldTotalCount + 1))));
				
				
			} else {
				terminologyGraph.add(new Triple(terminologyUriNode, rdfTypeNode, baseTerminologyNode));
				terminologyGraph.add(new Triple(terminologyUriNode, ontoSetLanguageNode, terminologyLanguageNode));				
				terminologyGraph.add(new Triple(terminologyUriNode, ontoHasLemmaNode, terminologyLemmaNode));
				terminologyGraph.add(new Triple(terminologyUriNode, 
						ontoOverallFrequencyNode, 
						Node.createLiteral(String.valueOf(terminItem.getFrequency()))));
				terminologyGraph.add(new Triple(terminologyUriNode, 
						ontoTotalCountNode,
						Node.createLiteral(String.valueOf(1))));
				
				for(Map.Entry<String, Integer> terminTextRepresentation : terminologyTextRepresentationSet.entrySet()) {					
						terminologyGraph.add(new Triple(terminologyUriNode, 
								ontoHasStringRepresentationNode, 
								Node.createLiteral(terminTextRepresentation.getKey())));					
				}
				
			}
		}		
	}
	
	public void persistDocument(VirtGraph graph, IExtractedKnowledge extractedKnowledge, Integer termCount, Integer predicateCount, File documentFile) {
		
		graph.clear();
		
		Node baseDocumentPrefixNode = Node.createURI(virtuosoUtils.getKnownGraphsUris().get("document"));
				
		Node classDocumentNode = Node.createURI(virtuosoUtils.getOntologyNodes().get("Document"));		
		Node ontoHasName = Node.createURI(virtuosoUtils.getOntologyNodes().get("hasName"));
		Node ontoHasLocation = Node.createURI(virtuosoUtils.getOntologyNodes().get("hasLocation"));
		
		Node ontoHasTermCount = Node.createURI(virtuosoUtils.getOntologyNodes().get("hasTermCount"));
		Node ontoHasPredicateCount = Node.createURI(virtuosoUtils.getOntologyNodes().get("hasPredicateCount"));
		Node ontoHasTerminologyCount = Node.createURI(virtuosoUtils.getOntologyNodes().get("hasTerminologyCount"));
		Node ontoHasSentenceCount = Node.createURI(virtuosoUtils.getOntologyNodes().get("hasSentenceCount"));		
		
		Node rdfTypeNode = Node.createURI(virtuosoUtils.getOntologyNodes().get("a"));
		
		String documentName = virtuosoUtils.escape(documentFile.getName());
		documentName = documentName.replaceAll(" ", "_");
		documentName = documentName.replace(".^[a-zA-Z]+$", "");
		
		Node documentUriNode = Node.createURI(baseDocumentPrefixNode.getURI() + documentName);
				
		graph.add(new Triple(documentUriNode, rdfTypeNode, classDocumentNode));
		
		graph.add(new Triple(documentUriNode, ontoHasName, Node.createLiteral(documentFile.getName())));
		graph.add(new Triple(documentUriNode, ontoHasLocation, Node.createLiteral(documentFile.getAbsolutePath())));
		
		graph.add(new Triple(documentUriNode, ontoHasTermCount, Node.createLiteral(String.valueOf(termCount))));
		graph.add(new Triple(documentUriNode, ontoHasPredicateCount, Node.createLiteral(String.valueOf(predicateCount))));
		graph.add(new Triple(documentUriNode, 
				ontoHasTerminologyCount, 
				Node.createLiteral(String.valueOf(extractedKnowledge.getTerminology().size()))));
		graph.add(new Triple(documentUriNode, 
				ontoHasSentenceCount, 
				Node.createLiteral(String.valueOf(extractedKnowledge.getSentencesCount()))));
		
		
		
		addTriples(graph, extractedKnowledge.getTriples());
		addTerminology(graph, extractedKnowledge.getTerminology());
	}
	
	private void addTriples(VirtGraph graph, List<ITripleItem> triples) {
		
		Node baseTermPrefixNode = Node.createURI(virtuosoUtils.getKnownGraphsUris().get("terms"));
		Node basePredPrefixNode = Node.createURI(virtuosoUtils.getKnownGraphsUris().get("predicates"));		
		
		for(ITripleItem triple : triples) {
			
			String subjectLemmaUri = "";
			String predicateLemmaUri = "";
			String objectLemmaUri = "";
			
			if (triple.getSubjectLemma() != null) {
				subjectLemmaUri = virtuosoUtils.escape(triple.getSubjectLemma().replaceAll(" ", "_"));
			}
			if (triple.getPredicateLemma()  != null) {
				predicateLemmaUri = virtuosoUtils.escape(triple.getPredicateLemma().replaceAll(" ", "_"));
			}
			if (triple.getObjectLemma()  != null) {
				objectLemmaUri = virtuosoUtils.escape(triple.getObjectLemma().replaceAll(" ", "_"));
			}
			
			
			if(subjectLemmaUri.isEmpty()) {
				continue;
			}
			if(predicateLemmaUri.isEmpty()) {
				predicateLemmaUri = "blank";
			}
			if(objectLemmaUri.isEmpty()) {
				objectLemmaUri = "blank";
			}
			
			Node subjectUriNode = Node.createURI(baseTermPrefixNode.getURI() + subjectLemmaUri);
			Node predicateUriNode = Node.createURI(basePredPrefixNode.getURI() + predicateLemmaUri);
			Node objectUriNode = Node.createURI(baseTermPrefixNode.getURI() + objectLemmaUri);
			
			graph.add(new Triple(subjectUriNode, predicateUriNode, objectUriNode));
		}	 
		
	}
	
	private void addTerminology(VirtGraph graph, List<ITerminologyItem> terminologyList) {
		
		Node baseTerminologyPrefixNode = Node.createURI(virtuosoUtils.getKnownGraphsUris().get("terminology"));
		Node ontoFrequencyNode = Node.createURI(virtuosoUtils.getOntologyNodes().get("hasFrequency"));
		
		for(ITerminologyItem terminologyItem : terminologyList) {
			String terminologyLemmaUri = terminologyItem.getLemma().replaceAll(" ", "_");
			terminologyLemmaUri = terminologyLemmaUri.trim();
			
			Node terminologyUriNode = Node.createURI(baseTerminologyPrefixNode.getURI() + terminologyLemmaUri);
			graph.add(new Triple(terminologyUriNode, 
					ontoFrequencyNode, 
					Node.createLiteral(String.valueOf(terminologyItem.getFrequency()))));
		}		
		
	}
}
